package net.xdclass.order.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import net.xdclass.common.JsonData;
import net.xdclass.common.PageJsonData;
import net.xdclass.common.constant.Cons;
import net.xdclass.common.constant.LogicDeleteCons;
import net.xdclass.common.enums.BizCodeEnum;
import net.xdclass.common.enums.ProductOrderStateEnum;
import net.xdclass.common.exception.BizException;
import net.xdclass.common.message.MessageInfo;
import net.xdclass.common.model.LoginUser;
import net.xdclass.common.utils.CommonUtil;
import net.xdclass.local.ThreadLocal;
import net.xdclass.order.component.PayFactory;
import net.xdclass.order.enums.CouponUseStatus;
import net.xdclass.order.enums.ProductOrderPayTypeEnum;
import net.xdclass.order.enums.ProductOrderTypeEnum;
import net.xdclass.order.feign.CouponFeignClient;
import net.xdclass.order.feign.ProductFeignClient;
import net.xdclass.order.feign.UserFeignClient;
import net.xdclass.order.mapper.ProductOrderItemMapper;
import net.xdclass.order.message.OrderMessageInfo;
import net.xdclass.order.message.OrderMessageProducer;
import net.xdclass.order.model.ProductOrderDO;
import net.xdclass.order.mapper.ProductOrderMapper;
import net.xdclass.order.model.ProductOrderItemDO;
import net.xdclass.order.model.request.ComfirmOrderRequest;
import net.xdclass.order.model.request.LockCouponRecordRequest;
import net.xdclass.order.model.request.LockProductRequest;
import net.xdclass.order.model.request.OrderItemRequest;
import net.xdclass.order.model.vo.*;
import net.xdclass.order.service.ProductOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author yaya
 * @since 2023-12-31
 */
@Slf4j
@Service
public class ProductOrderServiceImpl extends ServiceImpl<ProductOrderMapper, ProductOrderDO> implements ProductOrderService {

    @Autowired
    private ProductOrderMapper productOrderMapper;
    @Autowired
    private ProductOrderItemMapper productOrderItemMapper;
    @Autowired
    private UserFeignClient userFeignClient;
    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private CouponFeignClient couponFeignClient;
    @Autowired
    private OrderMessageProducer orderMessageProducer;
    @Autowired
    private PayFactory payFactory;

    /**
     * 查询我的订单
     *
     * @param currentPage
     * @param size
     * @param state
     * @return
     */
    @Override
    public JsonData page(int currentPage, int size, ProductOrderStateEnum state) {
        LoginUser loginUser = ThreadLocal.getLoginUser();
        LambdaQueryWrapper<ProductOrderDO> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ProductOrderDO::getUserId, loginUser.getId());
        if (state != null){
            wrapper.eq(ProductOrderDO::getState, state);
        }
        IPage page = new Page(currentPage, size);
        IPage<ProductOrderDO> orderList = productOrderMapper.selectPage(page, wrapper);
        List<ProductOrderVO> records = orderList.getRecords().stream().map(order -> {
            List<ProductOrderItemDO> orderItem = productOrderItemMapper.selectList(new LambdaQueryWrapper<ProductOrderItemDO>()
                    .eq(ProductOrderItemDO::getProductOrderId, order.getId()));
            List<OrderItemVO> collect = orderItem.stream().map(item -> {
                OrderItemVO orderItemVO = new OrderItemVO();
                BeanUtils.copyProperties(item, orderItemVO);
                return orderItemVO;
            }).collect(Collectors.toList());

            ProductOrderVO vo = toVO(order);
            vo.setOrderItem(collect);
            return vo;
        }).collect(Collectors.toList());
        PageJsonData pageJsonData = new PageJsonData(currentPage, size, (int) orderList.getTotal());
        pageJsonData.setData(records);
        return pageJsonData;
    }

    private ProductOrderVO toVO(ProductOrderDO order) {
        if (order != null){
            ProductOrderVO productOrderVO = new ProductOrderVO();
            BeanUtils.copyProperties(order, productOrderVO);
            return productOrderVO;
        }
        return null;
    }

    /**
     * 处理支付回调
     *
     * @param payType
     * @param params
     * @return
     */
    @Override
    public JsonData handleOrderCallback(ProductOrderPayTypeEnum payType, Map<String, String> params) {
        if (payType == null || params == null || params.isEmpty()){
            return JsonData.buildResult(BizCodeEnum.PAY_ORDER_CALLBACK_NOT_SUCCESS);
        }
        if (ProductOrderPayTypeEnum.ALIPAY.equals(payType)){
            String outTradeNo = params.get("out_trade_no");
            String tradeStatus = params.get("trade_status");
            if (StringUtils.isNotEmpty(tradeStatus) && ("TRADE_SUCCESS".equalsIgnoreCase(tradeStatus) || "TRADE_FINISHED".equalsIgnoreCase(tradeStatus))){
                int line = productOrderMapper.updateOrderState(outTradeNo, ProductOrderStateEnum.PAY, ProductOrderStateEnum.NEW);
                if (line == 1){
                    return JsonData.buildSuccess();
                }
            }
        }else if (ProductOrderPayTypeEnum.WECHAT.equals(payType)){
            // TODO
        }
        return JsonData.buildResult(BizCodeEnum.PAY_ORDER_CALLBACK_NOT_SUCCESS);
    }

    /**
     * 关闭订单
     *
     * @param messageInfo
     * @return
     */
    @Override
    public boolean closeOrder(MessageInfo messageInfo) {
        if (!(messageInfo instanceof OrderMessageInfo)){
            return true;
        }
        OrderMessageInfo message = (OrderMessageInfo) messageInfo;
        LambdaQueryWrapper<ProductOrderDO> wrapper = Wrappers.<ProductOrderDO>lambdaQuery().eq(ProductOrderDO::getOutTradeNo, message.getOutTradeNo());
        ProductOrderDO productOrder = productOrderMapper.selectOne(wrapper);
        if (productOrder == null){
            log.warn("订单不存在【{}】", message);
            return true;
        }
        ProductOrderStateEnum state = productOrder.getState();
        if (ProductOrderStateEnum.PAY.equals(state)){
            log.info("订单已支付【{}】", productOrder);
            return true;
        }
        // 向第三方二次确认订单是否支付
        PayInfoVO payInfoVO = new PayInfoVO();
        payInfoVO.setOutTradeNo(message.getOutTradeNo());
        payInfoVO.setPayType(productOrder.getPayType());
        String payResult = payFactory.queryOrderState(payInfoVO);
        if (StringUtils.isNotEmpty(payResult) && ("TRADE_SUCCESS".equalsIgnoreCase(payResult) || "TRADE_FINISHED".equalsIgnoreCase(payResult))){
            int rows = productOrderMapper.updateOrderState(productOrder.getOutTradeNo(), ProductOrderStateEnum.PAY, ProductOrderStateEnum.NEW);
            if (rows != 1) {
                log.warn("订单状态同步失败【{}】", productOrder);
                throw new BizException(BizCodeEnum.ORDER_STATE_SYNC_FAIL);
            }
        }else {
            // 未支付，取消订单
            // 支付方已支付，本地状态没有同步，需要将本地的订单状态改为已支付【可能的原因是支付方回调出了问题】
            log.warn("支付方回调可能异常，需要检查");
            int rows = productOrderMapper.updateOrderState(productOrder.getOutTradeNo(), ProductOrderStateEnum.CANCEL, ProductOrderStateEnum.NEW);
            if (rows != 1){
                log.warn("取消订单失败【{}】", productOrder);
                throw new BizException(BizCodeEnum.ORDER_CANCEL_FAIL);
            }
        }
        return true;
    }

    /**
     * 查询订单状态
     *
     * @param outTradeNo
     * @return
     */
    @Override
    public ProductOrderStateEnum selectOrderState(String outTradeNo) {
        LambdaQueryWrapper<ProductOrderDO> wrapper = Wrappers.<ProductOrderDO>lambdaQuery().eq(ProductOrderDO::getOutTradeNo, outTradeNo);
        ProductOrderDO productOrderDO = productOrderMapper.selectOne(wrapper);
        if (productOrderDO != null){
            return productOrderDO.getState();
        }
        return null;
    }

    /**
     * 提交订单
     *
     * @param order
     * @return
     */
    @Transactional
    @Override
    public JsonData comfirmOrder(ComfirmOrderRequest order) {
        /**
         * * 防重提交
         *   * 用户微服务-确认收货地址
         *   * 商品微服务-获取最新购物项和价格
         *   * 订单验价
         *     * 优惠券微服务-获取优惠券
         *     * 验证价格
         *   * 锁定优惠券
         *   * 锁定商品库存
         *   * 创建订单对象
         *   * 创建子订单对象
         *   * 发送延迟消息-用于自动关单
         *   * 创建支付信息-对接三方支付
         */
        LoginUser loginUser = ThreadLocal.getLoginUser();
        String outTradeNo = CommonUtil.getRandomStr(32);
        AddressVO address = getUserAddress(order.getAddressId());
        log.info("收货地址信息【{}】", address);
        // 获取商品信息
        List<Long> productIds = order.getProductIds();
        JsonData data = productFeignClient.findProduct(productIds);
        List<OrderItemVO> orderItems = data.getData(new TypeReference<>(){});
        if (CollectionUtils.isEmpty(orderItems)){
            return JsonData.buildResult(BizCodeEnum.ORDER_CONFIRM_CART_ITEM_NOT_EXIST);
        }
        log.info("购物车开始结算：【{}】", orderItems);
        // 验价
        checkPrice(orderItems, order);
        // 锁定优惠券
        lockCouponRecord(order, outTradeNo);
        // 锁定库存
        lockProductStock(orderItems, outTradeNo);
        // 创建订单
        ProductOrderDO productOrder = saveProductOrder(order, loginUser, outTradeNo, address);
        // 创建订单项
        saveProductOrderItem(orderItems, outTradeNo, productOrder.getId());
        // 延迟消息，自动关单
        sendMQMsg(outTradeNo);
        // 创建订单
        PayInfoVO payInfo = new PayInfoVO();
        payInfo.setOutTradeNo(outTradeNo);
        payInfo.setPayType(order.getPayType());
        payInfo.setPayFee(productOrder.getPayAmount());
        payInfo.setClientType(order.getClientType());
        payInfo.setTitle(outTradeNo);
        payInfo.setOrderPayTimeout(Cons.PAY_TIMEOUT);
        String redirectUrl = payFactory.pay(payInfo);
        if (StringUtils.isEmpty(redirectUrl)){
            log.warn("创建支付订单失败。【{}】", payInfo);
            return JsonData.buildResult(BizCodeEnum.PAY_ORDER_FAIL);
        }
        return JsonData.buildSuccess(redirectUrl);
    }

    /**
     * 发送自动关单消息
     * @param outTradeNo
     */
    private void sendMQMsg(String outTradeNo) {
        OrderMessageInfo messageInfo = new OrderMessageInfo();
        messageInfo.setOutTradeNo(outTradeNo);
        messageInfo.setTime(new Date());
        orderMessageProducer.sendMsg(messageInfo);
    }

    /**
     * 创建订单项
     * @param orderItems
     * @param outTradeNo
     * @param productOrderId
     */
    private void saveProductOrderItem(List<OrderItemVO> orderItems, String outTradeNo, Long productOrderId) {
        if (CollectionUtils.isEmpty(orderItems) || StringUtils.isEmpty(outTradeNo) || ObjectUtils.isEmpty(productOrderId)){
            return;
        }
        List<ProductOrderItemDO> collect = orderItems.stream().map(item -> {
            ProductOrderItemDO orderItem = new ProductOrderItemDO();
            orderItem.setProductId(item.getProductId());
            orderItem.setProductOrderId(productOrderId);
            orderItem.setProductImg(item.getProductImg());
            orderItem.setAmount(item.getAmount());
            orderItem.setBuyNum(item.getBuyNum());
            orderItem.setCreateTime(new Date());
            orderItem.setProductName(item.getProductName());
            orderItem.setTotalAmount(item.getTotalAmount());
            orderItem.setOutTradeNo(outTradeNo);
            return orderItem;
        }).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(collect)){
            return;
        }
        int rows = productOrderItemMapper.insertBatch(collect);
        if (rows != collect.size()){
            throw new BizException(BizCodeEnum.ORDER_ITEM_CREATE_FAIL);
        }

    }

    /**
     * 创建订单
     * @param request
     * @param loginUser
     * @param outTradeNo
     * @param address
     */
    private ProductOrderDO saveProductOrder(ComfirmOrderRequest request, LoginUser loginUser, String outTradeNo, AddressVO address) {
        if (request == null || loginUser == null || StringUtils.isEmpty(outTradeNo) || address == null){
            throw new BizException(BizCodeEnum.ORDER_CREATE_FAIL);
        }
        ProductOrderDO order = new ProductOrderDO();
        order.setUserId(loginUser.getId());
        order.setNickname(loginUser.getName());
        order.setHeadImg(loginUser.getHeadImg());

        order.setOrderType(ProductOrderTypeEnum.DAILY);
        order.setDel(LogicDeleteCons.NOT);
        order.setOutTradeNo(outTradeNo);
        order.setCreateTime(new Date());

        order.setPayAmount(request.getRealAmount());
        order.setTotalAmount(request.getTotalAmount());
        order.setPayType(request.getPayType());
        order.setState(ProductOrderStateEnum.NEW);

        order.setReceiverAddress(JSON.toJSONString(address));

        int rows = productOrderMapper.insert(order);
        if (rows != 1){
            throw new BizException(BizCodeEnum.ORDER_CREATE_FAIL);
        }
        return order;
    }

    /**
     * 锁定商品库存
     * @param orderItems
     * @param outTradeNo
     */
    private void lockProductStock(List<OrderItemVO> orderItems, String outTradeNo) {
        if (CollectionUtils.isEmpty(orderItems) || StringUtils.isEmpty(outTradeNo)){
            return;
        }
        List<OrderItemRequest> orderItemRequests = orderItems.stream().map(item -> {
            OrderItemRequest request = new OrderItemRequest();
            request.setProductId(item.getProductId());
            request.setBuyNum(item.getBuyNum());
            return request;
        }).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(orderItemRequests)){
            return;
        }
        LockProductRequest request = new LockProductRequest();
        request.setOrderItemList(orderItemRequests);
        request.setOrderOutTradeNo(outTradeNo);
        JsonData data = productFeignClient.lockProduct(request);
        if (data == null || !Cons.SUCCESS.equals(data.getCode())){
            log.warn("商品库存锁定失败【{}】", orderItems);
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_LOCK_PRODUCT_FAIL);
        }
    }

    /**
     * 锁定优惠券
     * @param orderRequest
     * @param outTradeNo
     */
    private void lockCouponRecord(ComfirmOrderRequest orderRequest, String outTradeNo) {
        if (orderRequest == null || orderRequest.getCouponRecordId() == null ||
                orderRequest.getCouponRecordId() <= 0 || StringUtils.isEmpty(outTradeNo)){
            return;
        }
        LockCouponRecordRequest request = new LockCouponRecordRequest();
        request.setOrderOutTradeNo(outTradeNo);
        request.setLockCouponRecordIds(Collections.singletonList(orderRequest.getCouponRecordId()));
        JsonData data = couponFeignClient.lockCouponRecord(request);
        if (data == null || !Cons.SUCCESS.equals(data.getCode())){
            log.warn("优惠券锁定失败【{}】", orderRequest.getCouponRecordId());
            throw new BizException(BizCodeEnum.COUPON_RECORD_LOCK_FAIL);
        }
    }

    /**
     * 验价
     * 1、计算所有订单总价
     * 2、计算优惠券
     *      - 是否达到满减价格
     *      - 支付价格是否小于优惠券价格
     * @param orderItems
     * @param order
     */
    private void checkPrice(List<OrderItemVO> orderItems, ComfirmOrderRequest order) {
        if (CollectionUtils.isEmpty(orderItems)){
            return;
        }
        BigDecimal realPayAmount = new BigDecimal("0");
        for (OrderItemVO orderItem : orderItems) {
            BigDecimal itemAmount = orderItem.getTotalAmount();
            realPayAmount = realPayAmount.add(itemAmount);
        }
        // 获取优惠券
        CouponRecordVO couponRecord = getCartCouponRecord(order.getCouponRecordId());
        if (couponRecord != null){
            // 没有达到满减价格
            if (realPayAmount.compareTo(couponRecord.getConditionPrice()) < 0){
                throw new BizException(BizCodeEnum.ORDER_CONFIRM_COUPON_FAIL);
            }
            // 实际支付价格小于优惠券价格 -> 实际支付价格 = 0
            if (realPayAmount.compareTo(couponRecord.getPrice()) < 0){
                realPayAmount = BigDecimal.ZERO;
            }else {
                BigDecimal couponAmount = couponRecord.getPrice();
                realPayAmount = realPayAmount.subtract(couponAmount);
            }
        }
        if (realPayAmount.compareTo(order.getRealAmount()) != 0){
            log.warn("订单验价失败，预期价格【{}】，实际价格【{}】", order.getRealAmount(), realPayAmount);
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_PRICE_FAIL);
        }
    }

    /**
     * 根据id获取优惠券
     * 1、判断优惠券是否在可用时间范围
     * @param couponRecordId
     * @return
     */
    private CouponRecordVO getCartCouponRecord(Long couponRecordId) {
        if (couponRecordId == null || couponRecordId <= 0){
            return null;
        }
        JsonData data = couponFeignClient.recordDetail(couponRecordId);
        if (data == null || !Cons.SUCCESS.equals(data.getCode())){
            return null;
        }
        CouponRecordVO coupon = data.getData(new TypeReference<>(){});
        if (!availableCoupon(coupon)){
            log.warn("优惠券不可用【{}】", coupon);
            throw new BizException(BizCodeEnum.COUPON_UNAVAILABLE);
        }
        return coupon;
    }

    /**
     * 优惠券是否可用
     * @param coupon
     * @return
     */
    private boolean availableCoupon(CouponRecordVO coupon) {
        if (coupon == null){
            return false;
        }
        if (!CouponUseStatus.NEW.equals(coupon.getUseState())){
            return false;
        }
        long start = coupon.getStartTime().getTime();
        long end = coupon.getEndTime().getTime();
        long currnetTime = CommonUtil.getCurrnetTimeStamp();
        if (start <= currnetTime && end >= currnetTime){
            return true;
        }
        return false;
    }

    /**
     * 获取用户地址信息
     * @param addressId
     * @return
     */
    private AddressVO getUserAddress(Long addressId) {
        JsonData data = userFeignClient.detail(addressId);
        if (data != null && Cons.SUCCESS.equals(data.getCode())){
            AddressVO address = data.getData(new TypeReference<>(){});
            return address;
        }else {
            throw new BizException(BizCodeEnum.ADDRESS_NO_EXITS);
        }
    }
}
